# -*- encoding：utf-8 -*-
"""
-文件：utxo_set.py
-说明：UTXOSet类
-作者：tlj

UTXO 集是一个从区块链中所有交易构建而来的缓存，
它仅缓存包含可花费输出的交易，
因此其大小也会远远小于整个区块链的数据空间，
用它来计算余额和验证新的交易的效率也就比较高：
"""
import collections

from .db import DB
from transaction.transaction import *


class TransactionNotFound(Exception):
    """未找到指定交易的错误"""
    pass


class UTXOSet:
    DB_FILE = "data/bc_utxoset_{}.db"

    def __init__(self, blockchain):
        self._db = DB(UTXOSet.DB_FILE.format(blockchain.node_id))
        self.blockchain = blockchain

    def rebuild(self):
        """
        清空重置数据库
        :return:
        """
        self._db.reset()
        # 所有未花费交易
        utxos = self.blockchain.find_utxo1()

        # 将未花费交易放入UTXOSet
        for tx_id, out_list in utxos.items():
            # key: 交易id， value： 可花费输出
            self._db.put(tx_id, out_list)

        self._db.commit()

    def find_spendable_outputs(self, address, amount):
        """
        寻找可花费输出
        :param address:
        :param amount:
        :return:
        """
        accumulated = 0
        unspent_outputs = collections.defaultdict(list)
        for tx_id, outs in self._db.kv.items():
            # for idx, out in enumerate(outs):
            for out, idx in outs:
                if out.is_locked_with(address) and accumulated<amount:
                    accumulated += out.amount
                    unspent_outputs[tx_id].append(idx)
        return accumulated, unspent_outputs

    def find_utxo(self, address):
        """
        寻找所有属于该地址的输出
        :param address:
        :return:
        """
        utxos = []
        for outs in self._db.kv.values():
            for out,idx in outs:
                if out.is_locked_with(address):
                    utxos.append(out)
        return utxos

    def find_transaction_utxo(self, tx_id):
        """
        寻找指定交易id的交易
        :param tx_id:
        :return:
        """
        try:
            return self._db.kv[tx_id]
        except KeyError:
            raise TransactionNotFound()

    def update(self, new_block):
        """
        更新UTXOSet
        :param new_block:
        :return:
        """
        # print(type(new_block.data), len(new_block.data),type(new_block.data[0]),
        #       type(new_block.data[1]))
        for tx in new_block.data:
            if not isinstance(tx, CoinbaseTx):
                for vin in tx.vins:
                    update_outs = []
                    # 获取输入引用的输出
                    outs = self._db.get(vin.tx_id)

                    for out, idx in outs:
                        if idx != vin.vout_idx:
                            update_outs.append([out, idx])
                    if len(update_outs) == 0:
                        self._db.delete(vin.tx_id)
                    else:
                        self._db.put(vin.tx_id, update_outs)

            new_outputs = [[tx.vouts[i], i] for i in range(len(tx.vouts))]
            self._db.put(tx.id_, new_outputs)

        self._db.commit()

    def print_utxo_set(self):
        print('UTXO SET:    ')
        for tx_id, outs in self._db.kv.items():
            print("{}:{}".format(tx_id, outs))
        print()
















